package com.sl.core.engine.log;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.sl.core.utils.JsonUtil;
import com.sl.core.engine.meta.trans.ProcessTransDescriptor;
import com.sl.core.engine.status.ProcessExecutorCompInfo;
import com.sl.core.engine.step.impl.AbstractProcessComp;
import com.sl.core.engine.trans.ProcessInstance;
import com.sl.core.engine.utils.BeanUtil;
import org.apache.commons.compress.utils.Lists;
import org.apache.hop.core.logging.*;
import org.slf4j.MDC;

import java.util.*;

public class ProcessLogLayout extends HopLogLayout {

    private Map<String, String> patternMap = new HashMap<>();

    public ProcessLogLayout() {
        patternMap.put("root", "\u0000");
        patternMap.put("timestamp", "\u0001");
        patternMap.put("processName", "\u0002");
        patternMap.put("processVersion", "\u0003");
        patternMap.put("logLevel", "\u0004");
        patternMap.put("stepName", "\u0005");
        patternMap.put("message", "\u0006");
//        patternMap.put("address", "\u0007");
//        patternMap.put("param", "\u0008");
//        patternMap.put("result", "\u0010");

        patternMap.put("processId", "\u0011");
        patternMap.put("tid", "\u0012");

        patternMap.put("threadId", "\u0013");
        patternMap.put("threadName", "\u0014");
        patternMap.put("stepStatus", "\u0015");

    }

    public String format(HopLoggingEvent event) {
        if (ObjectUtil.isNull(event)) {
            return null;
        }
        Boolean logDetailFlag = false;
        Boolean wrapperSpecChar = true;
        if (event instanceof ProcessLoggingEvent ev) {
            logDetailFlag = ev.getLogDetailFlag();
            wrapperSpecChar = ev.getWrapperSpecCharFlag();
        }
        if (ObjectUtil.isNull(logDetailFlag)) {
            logDetailFlag = false;
        }
        if (ObjectUtil.isNull(wrapperSpecChar)) {
            wrapperSpecChar = true;
        }

        StringBuilder line = new StringBuilder();
        if (BooleanUtil.isTrue(wrapperSpecChar)) {
            line.append(patternMap.get("root"));
        }

        Object object = event.getMessage();
        String dateTimeString = "";
        if (isTimeAdded()) {
            dateTimeString = DateUtil.format(new Date(event.timeStamp), DatePattern.NORM_DATETIME_MS_FORMAT);
        }

        if (object instanceof LogMessage) {
            LogMessage message = (LogMessage) object;
            String processName = "";
            String processVersion = "";
            String stepName = message.getSubject();
            String processId = null;
            String threadId = null;
            String threadName = null;
            String parentPath = null;
            Long delay = null;
            String tid = "";
//            List<ProcessExecutorStepDTO> executorStepList = null;
            if (message instanceof ProcessLogMessage msg) {
                threadId = msg.getThreadId();
                threadName = msg.getThreadName();
//                executorStepList = ((OpenSkyLogMessage) message).getExecutorStepList();
                ILoggingObject logChannelHooks = msg.getLogChannelHooks();
                if (ObjectUtil.isNotNull(logChannelHooks)) {
                    if (logChannelHooks instanceof ProcessInstance processInstance) {
                        processName = processInstance.getName();
                        processVersion = processInstance.getProcessVersion();
                        processId = processInstance.getProcessId();
                        tid = processInstance.getTid();
                    } else if (logChannelHooks instanceof AbstractProcessComp comp) {
                        processName = comp.getPipeline().getObjectName();
                        processVersion = comp.getPipeline().getObjectName();
                        stepName = comp.getTransformName();
                        processId = comp.getPipeline().getProcessId();
                        tid = comp.getPipeline().getTid();
                    }
                    if (logChannelHooks instanceof ProcessTransDescriptor meta) {
                        processVersion = meta.getProcessVersion();
                        processName = meta.getName();
                        processId = meta.getProcessId();
                        tid = MDC.get(ProcessInstance.TID_IDENTIFY);
                    }
                }
            }

            if (StrUtil.equals(stepName, processName)) {
                stepName = "";
            }
            LogLevel level = message.getLevel();
            String logLevelStr = "INFO";
            if (level == LogLevel.BASIC || level == LogLevel.MINIMAL) {
                logLevelStr = "INFO";
            } else if (level == LogLevel.ERROR) {
                logLevelStr = "ERROR";
            } else if (level == LogLevel.DETAILED || level == LogLevel.ROWLEVEL || level == LogLevel.DEBUG) {
                logLevelStr = "DEBUG";
            }

            String messageStr = message.getMessage();
            messageStr = StrUtil.nullToDefault(messageStr, StrUtil.EMPTY);

            line.append(warpAndClear(StrUtil.nullToDefault(threadId, StrUtil.EMPTY), "threadId", wrapperSpecChar));
            line.append(" - ");
            line.append(warpAndClear(StrUtil.nullToDefault(threadName, StrUtil.EMPTY), "threadName", wrapperSpecChar));
            line.append(" - ");
            //如果要更详细的 需要 processId trackId threadId
            if (BooleanUtil.isTrue(logDetailFlag)) {
                line.append(warpAndClear(StrUtil.nullToDefault(processId, StrUtil.EMPTY), "processId", wrapperSpecChar));
                line.append(" - ");
                line.append(warpAndClear(StrUtil.nullToDefault(tid, StrUtil.EMPTY), "tid", wrapperSpecChar));
                line.append(" - ");
            }
            line.append(warpAndClear(dateTimeString, "timestamp", wrapperSpecChar));
            line.append(" - ");

            line.append(warpAndClear(processName, "processName", wrapperSpecChar));
            line.append(" - ");
            if (StrUtil.isNotBlank(processVersion)) {
                line.append(warpAndClear(processVersion, "processVersion", wrapperSpecChar));
                line.append(" - ");
            }
            line.append(warpAndClear(logLevelStr, "logLevel", wrapperSpecChar));
            line.append(" - ");

            if (StrUtil.isNotBlank(stepName)) {
                line.append(warpAndClear(stepName, "stepName", wrapperSpecChar));
                line.append(" - ");
            }

            line.append(warpAndClear(messageStr, "message", wrapperSpecChar));

        } else {
            line.append(warpAndClear(dateTimeString, "timestamp", wrapperSpecChar));
            line.append(" - ");
            line.append((object != null ? object.toString() : "<null>"));
        }
        if (BooleanUtil.isTrue(wrapperSpecChar)) {
            line.append(patternMap.get("root"));
        }
        return line.toString();

    }

    private Map<String, Object> findParentNamePath(ProcessInstance processInstance) {
        StringJoiner stringJoiner = new StringJoiner("->");
        Map<String, Object> parentMap = Maps.newHashMap();
        ProcessInstance cus = processInstance;
        while (true) {
            ILoggingObject parent = cus.getParent();
            if (ObjectUtil.isNull(parent)) {
                break;
            }
            if (!(parent instanceof ProcessInstance process)) {
                break;
            }
            cus = process;
            stringJoiner.add(process.getProcessName());
        }
        return parentMap;
    }

    private Map<String, Object> findParentInfo(ProcessInstance processInstance) {
        Map<String, Object> rootMap = Maps.newHashMap();

        Map<String, Object> lastParentMap = Maps.newHashMap();
        rootMap.put("parent", lastParentMap);

        ILoggingObject cus = processInstance.getParent();
        while (true) {
            if (ObjectUtil.isNull(cus)) {
                break;
            }
            AbstractProcessComp abstractProcessComp = null;
            ProcessInstance process = null;
            if (cus instanceof AbstractProcessComp processComp) {
                abstractProcessComp = processComp;
                process = processComp.getPipeline();
            }

            Map<String, Object> parentMap = Maps.newHashMap();

            parentMap.put("processVersionId", process.getOpenId());
            parentMap.put("processId", process.getProcessId());
            parentMap.put("processName", process.getProcessName());
            parentMap.put("processVersion", process.getProcessVersion());
            if (ObjectUtil.isNotNull(abstractProcessComp)) {
                parentMap.put("stepCode", abstractProcessComp.getName());
            }
            lastParentMap.put("parent", parentMap);
            lastParentMap = parentMap;
            cus = process.getParent();
        }
        return BeanUtil.getMap(rootMap, "parent.parent");
    }


    private String warpAndClear(String str, String key, Boolean wrapperSpecChar) {
        //先对特殊字符进行替换
        for (Map.Entry<String, String> se : patternMap.entrySet()) {
            String value = se.getValue();
            str = replaceTes(str, value);
        }
        if (BooleanUtil.isFalse(wrapperSpecChar)) {
            return str;
        }
        //再进行包裹
        String s = patternMap.get(key);
        if (StrUtil.isBlank(s)) {
            return str;
        }
        return StrUtil.concat(true, s, str, s);
    }

    /**
     * 替换特殊字符
     *
     * @param str
     * @param cha
     * @return
     */
    private String replaceTes(String str, String cha) {
        if (StrUtil.isBlank(str)) {
            return str;
        }
        return StrUtil.replace(str, cha, StrUtil.EMPTY);
    }

    public Map<String, Object> formatJsonToMap(ProcessLoggingEvent event) {
        Map<String, Object> map = Maps.newHashMap();
        map.put("timestamp", DateUtil.format(new Date(event.timeStamp), DatePattern.NORM_DATETIME_MS_FORMAT));
        Object object = event.getMessage();

        if (!(object instanceof LogMessage)) {
            return map;
        }

        LogMessage message = (LogMessage) object;
        String processName = "";

        String processVersion = "";
        String stepName = message.getSubject();
        String processId = null;
        String threadId = null;
        Map<String, Object> parentInfo = null;
        String threadName = null;
        List<ProcessExecutorCompInfo> executorStepList = Lists.newArrayList();
        Object input = null;
        Object output = null;
        String tid = "";
//            List<ProcessExecutorStepDTO> executorStepList = null;
        if (message instanceof ProcessLogMessage msg) {
            threadId = msg.getThreadId();
            threadName = msg.getThreadName();
            executorStepList = msg.getExecutorStepList();
            input = msg.getStepInput();
            output = msg.getStepOutput();
//                executorStepList = ((OpenSkyLogMessage) message).getExecutorStepList();
            ILoggingObject logChannelHooks = msg.getLogChannelHooks();
            if (ObjectUtil.isNotNull(logChannelHooks)) {
                if (logChannelHooks instanceof ProcessInstance processInstance) {
                    processName = processInstance.getName();
                    processVersion = processInstance.getProcessVersion();
                    processId = processInstance.getProcessId();
                    tid = processInstance.getTid();
                    parentInfo = findParentInfo(processInstance);

                } else if (logChannelHooks instanceof AbstractProcessComp comp) {
                    processName = comp.getPipeline().getName();
                    processVersion = comp.getPipeline().getName();
                    stepName = comp.getTransformName();
                    processId = comp.getPipeline().getProcessId();
                    tid = comp.getPipeline().getTid();
                    parentInfo = findParentInfo(comp.getPipeline());
                }
                if (logChannelHooks instanceof ProcessTransDescriptor meta) {
                    processVersion = meta.getProcessVersion();
                    processName = meta.getName();
                    processId = meta.getProcessId();
                    tid = MDC.get(ProcessInstance.TID_IDENTIFY);
                }
            }
        }

        if (StrUtil.equals(stepName, processName)) {
            stepName = "";
        }
        LogLevel level = message.getLevel();
        String logLevelStr = "INFO";
        if (level == LogLevel.BASIC || level == LogLevel.MINIMAL) {
            logLevelStr = "INFO";
        } else if (level == LogLevel.ERROR) {
            logLevelStr = "ERROR";
        } else if (level == LogLevel.DETAILED || level == LogLevel.ROWLEVEL || level == LogLevel.DEBUG) {
            logLevelStr = "DEBUG";
        }

        String messageStr = message.getMessage();
        messageStr = StrUtil.nullToDefault(messageStr, StrUtil.EMPTY);

        map.put("processName", processName);
        map.put("processVersion", processVersion);
        map.put("logLevel", logLevelStr);
        map.put("stepName", stepName);
        map.put("message", messageStr);
        map.put("processId", processId);
        map.put("tid", tid);

        map.put("threadId", threadId);
        map.put("threadName", threadName);
        map.put("stepStatus", executorStepList);
        map.put("input", input);
        map.put("output", output);
        map.put("parent", parentInfo);

        return map;
    }

    public String formatJson(ProcessLoggingEvent event) {
        Map<String, Object> map = formatJsonToMap(event);
        return JsonUtil.toStr(map);
    }

    public ProcessInstance getPipeline(ProcessLoggingEvent event) {

        Object object = event.getMessage();

        if (!(object instanceof LogMessage)) {
            return null;
        }
        ProcessInstance ps = null;
        LogMessage message = (LogMessage) object;
        if (message instanceof ProcessLogMessage msg) {
//                executorStepList = ((OpenSkyLogMessage) message).getExecutorStepList();
            ILoggingObject logChannelHooks = msg.getLogChannelHooks();
            if (ObjectUtil.isNotNull(logChannelHooks)) {
                if (logChannelHooks instanceof ProcessInstance processInstance) {
                    ps = processInstance;

                } else if (logChannelHooks instanceof AbstractProcessComp comp) {
                    ps = comp.getPipeline();
                }
            }
        }
        return ps;
    }
}
